//
//  EntityManager.m
//  objectiverecord
//
//  Created by Gang.Wang on 12-5-18.
//  Copyright (c) 2012年 yuike. All rights reserved.
//

#import <objc/runtime.h>
#import "EntityManager.h"
#import "Entity+Local.h"
#import "header.h"
#import "ObjectiveRecordDef.h"


@interface EntityManager(private)

- (NSString *) or_buildFindSQL:(NSString *)className;
- (NSArray *)find:(NSString *)className withSql:(NSString *)sqlString;
- (void) delete:(NSString *)className withSql:(NSString *)sqlString;
- (NSInteger) countByTable:(NSString *)className;

- (NSArray *) rowidsByTable:(NSString *)className withfilter:(NSString *)filterString;
- (NSArray *) fieldsByTable:(NSString *) className  field:(NSString *) field withfliter:(NSString *) filterString;
@end


@implementation EntityManager

- (id)initWithDataPath:(NSString *)path {
  self = [super init];
  if(self) {
    dbPath = [path copy];
    dbProvider = [[DatabaseProvider alloc] initWithPath:dbPath];
  }
  return self;
}

- (void)finalize {
    
  [dbProvider close];
  [super finalize];
}

- (void)dealloc {
  [dbProvider close];
    
    [dbPath release];dbPath=nil;
    [dbProvider release];dbProvider=nil;
  [super dealloc];
}

- (NSString *)getdbPath {
  return dbPath;
}

- (NSString *)sqliteVersion {
  return [dbProvider sqliteVersion];
}

- (Entity *)create:(NSString *)className {
  
      Class clazz = NSClassFromString(className);
      unsigned int outCount;
      objc_property_t *properties = class_copyPropertyList(clazz, &outCount);
      NSMutableArray *column_name_array = [[[NSMutableArray alloc] init] autorelease];
      NSMutableArray *column_type_array = [[[NSMutableArray alloc] init] autorelease];
      
      for (int i = 0; i < outCount; i++) 
      {
        
        objc_property_t property = properties[i];
        const char *property_name = property_getName(property);
        const char *property_type = property_getAttributes(property);
        
        NSString *property_nameString =[NSString stringWithUTF8String:property_name];
        NSString *property_typeString = [NSString stringWithUTF8String:property_type];
        NSArray *attributes = [property_typeString componentsSeparatedByString:@","];
        NSString *typeAttribute = [attributes objectAtIndex:0];
        
        [column_name_array addObject:property_nameString];
        //NSLog(@"type: %c", property_type[1]);
        switch(property_type[1]) {
          case 'i' : //int
            [column_type_array addObject:@"INTEGER"];
            break;
          case 'f' : //float
            [column_type_array addObject:@"REAL"];
            break;
          case 'd' : //double
            [column_type_array addObject:@"REAL"];
            break;
          case 'q' : //NSInteger
            [column_type_array addObject:@"INTEGER"];
            break;
          case 'c' : //BOOL
            [column_type_array addObject:@"BOOL"];
            break;
          case '@' : //ObjC object
          {
            NSString *typeClassString = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)];
            if ([typeClassString isEqualToString:@"NSString"]) {
              [column_type_array addObject:@"TEXT"];
            } else if ([typeClassString isEqualToString:@"NSDate"]) {
              [column_type_array addObject:@"DATETIME"];
            } else if ([typeClassString isEqualToString:@"NSData"]) {
              [column_type_array addObject:@"BLOB"];
            }
          }
          break;
        }
      } 
        
        free(properties);
      
      NSString *sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@ (",[EntityManager getTableNameWithClassName:className]];
      for (int i = 0; i < [column_name_array count]; i++) {
        sql = [NSString stringWithFormat:@"%@ %@ %@", sql, [column_name_array objectAtIndex:i], [column_type_array objectAtIndex:i]];
        if (i < [column_name_array count]-1) {
          sql = [NSString stringWithFormat:@"%@, ", sql];
        }
      }
      sql = [NSString stringWithFormat:@"%@ )", sql];
      
      [dbProvider open];
      [dbProvider executeQuery:sql withArgument:nil];
      
      id createdClass = [[clazz alloc] init];
      Entity *entity = (Entity *)createdClass;
      [entity setDbProvider: dbProvider];
      [entity setTable_name: [EntityManager getTableNameWithClassName:className]];
      [entity setColumn_name_array: column_name_array];
      [entity setColumn_type_array: column_type_array];
      
      createdClass = nil;
    //  column_name_array = nil;
    //  column_type_array = nil;
      
      return [entity autorelease];
}

- (NSArray *)find:(NSString *)className 
{
  NSString* sql = [self or_buildFindSQL:className];
  return [self find: className withSql:sql];
}

- (NSArray *)find:(NSString *)className withArgument:(NSDictionary *) arg {
  NSString* sql = [self or_buildFindSQL:className];
  
  NSArray *keys = [arg allKeys];
  sql = [NSString stringWithFormat:@"%@ WHERE %@ %@ ", sql, [keys objectAtIndex:0], [arg objectForKey:[keys objectAtIndex:0]]];
  
  if([keys count] > 1) {
    for (int i = 1; i < [keys count]; i++) {
      sql = [NSString stringWithFormat:@"%@ AND %@ %@ ", sql, [keys objectAtIndex:i], [arg objectForKey:[keys objectAtIndex:i]]];
    }
  }
  return [self find: className withSql:sql];
}

- (void) delete:(NSString *)className {
  NSString* sql = [NSString stringWithFormat:@"DELETE FROM %@",[EntityManager getTableNameWithClassName: className]];
  return [self delete:className withSql:sql];
}

- (void) delete:(NSString *)className withArgument:(NSDictionary *) arg {
  NSString* sql = [NSString stringWithFormat:@"DELETE FROM %@",[EntityManager getTableNameWithClassName: className]];
  NSArray *keys = [arg allKeys];
  sql = [NSString stringWithFormat:@"%@ WHERE %@ %@ ", sql, [keys objectAtIndex:0], [arg objectForKey:[keys objectAtIndex:0]]];
  
  if([keys count] > 1) {
    for (int i = 1; i < [keys count]; i++) {
      sql = [NSString stringWithFormat:@"%@ AND %@ %@ ", sql, [keys objectAtIndex:i], [arg objectForKey:[keys objectAtIndex:i]]];
    }
  }
  return [self delete:className withSql:sql];
}

- (NSInteger) count:(NSString *)className
{
    return [self countByTable:className];
}

- (id) find:(NSString *)className rowid:(NSInteger) rowid
{
    if ([NSString isNilOrEmpty:className]) 
    {
        return nil;
    }
    if ([NSString isNilOrEmpty:className]) 
    {
        return nil;
    }
    
    NSDictionary * dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"=%d", rowid], 
                          @"rowid", 
                          nil];
    
    NSArray * rows = [self find:className withArgument:dic];
    if (!rows ||
        [rows count] <= 0) 
    {
        return nil;
    }
    
    return [rows objectAtIndex:0];
}

- (NSArray *) rowids:(NSString *)className withArgument:(NSDictionary *) arg;
{
    
    NSArray *keys = [arg allKeys];
    NSString * filterString  = [NSString stringWithString:@""];
    filterString = [NSString stringWithFormat:@"%@ %@ %@ ", filterString, [keys objectAtIndex:0], [arg objectForKey:[keys objectAtIndex:0]]];
    
    if([keys count] > 1) 
    {
        for (int i = 1; i < [keys count]; i++) 
        {
            filterString = [NSString stringWithFormat:@"%@ OR %@ %@ ", filterString, [keys objectAtIndex:i], [arg objectForKey:[keys objectAtIndex:i]]];

        }
    }
    
    return [self rowidsByTable:className withfilter:filterString];
}


- (NSArray *) find:(NSString *)className withField:(NSString *) field withArgument:(NSDictionary *) arg
{
    
    NSArray *keys = [arg allKeys];
    NSString * filterString  = [NSString stringWithString:@""];
    filterString = [NSString stringWithFormat:@"%@ %@ %@ ", filterString, [keys objectAtIndex:0], [arg objectForKey:[keys objectAtIndex:0]]];
    
    if([keys count] > 1) 
    {
        for (int i = 1; i < [keys count]; i++) 
        {
            filterString = [NSString stringWithFormat:@"%@ OR %@ %@ ", filterString, [keys objectAtIndex:i], [arg objectForKey:[keys objectAtIndex:i]]];
            
        }
    }

    
    
    return [self fieldsByTable:className field:field withfliter:filterString];
}



- (NSString *) processIdPropertyIfNeed:(NSString *) propertyName className:(NSString *) className
{
NSString * result = [[[NSString alloc] initWithString:propertyName] autorelease];
if ([NSString isNilOrEmpty:propertyName] ||
    [NSString isNilOrEmpty:className]) 
{
    return result;
}

NSString * strValue = [[className mutableCopy] autorelease];
strValue = [strValue replace:_OBJECTIVEREVORD_TABLE_ with:@""];
strValue = [strValue stringByAppendingString:_OBJECTIVEREVORD_PROPERTY_ID_];

if ([strValue isEqualToString:propertyName]) 
{
    result = _OBJECTIVEREVORD_ID_;
}        

return result ;
}

+ (NSString *) getTableNameWithClassName:(NSString * ) className
{
    if ([NSString isNilOrEmpty:className]) 
    {
        return nil;
    }
    
    
    NSString * reslut = [className replace:_OBJECTIVEREVORD_TABLE_ with:@""];
    return reslut;
}


-(NSString *) or_buildFindSQL:(NSString *)className {
    NSString* sql = [NSString stringWithFormat:@"SELECT rowid, "];
    Class clazz = NSClassFromString(className);
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList(clazz, &outCount);
    
    for (int i = 0; i < outCount; i++) 
    {
        objc_property_t property = properties[i];
        const char *property_name = property_getName(property);
        NSString *property_nameString =[NSString stringWithUTF8String:property_name];
        property_nameString  = [self processIdPropertyIfNeed:property_nameString className:className];
        sql = [NSString stringWithFormat:@"%@ %@", sql, property_nameString];
        if(i < (outCount - 1)) 
        {
            sql = [NSString stringWithFormat:@"%@, ",sql];
        }
    }
    free(properties);
    properties = nil;
    sql = [NSString stringWithFormat:@"%@ FROM %@", sql, [EntityManager getTableNameWithClassName:className]];
    return sql;
}

- (NSArray *)find:(NSString *)className withSql:(NSString *)sqlString {
   
    NSMutableArray *resultEntities = [[[NSMutableArray alloc] init] autorelease];
    
    Class clazz = NSClassFromString(className);
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList(clazz, &outCount);
    NSMutableArray *column_name_array = [[[NSMutableArray alloc] init] autorelease];
    NSMutableArray *column_type_array = [[[NSMutableArray alloc] init] autorelease];
    NSMutableDictionary *nameTypeDic  = [NSMutableDictionary dictionary];
    
    [column_name_array addObject:@"rowid"];
    [column_type_array addObject:@"int"];
    [nameTypeDic setObject:[column_type_array objectAtIndex:0] forKey:[column_name_array objectAtIndex:0]];
    
    for (int i = 0; i < outCount; i++) 
    {
        objc_property_t property = properties[i];
        const char *property_name = property_getName(property);
        const char *property_type = property_getAttributes(property);
        
//        NSString *property_nameString =[NSString stringWithUTF8String:property_name];
//        NSString *property_typeString = [NSString stringWithUTF8String:property_type];
        
        NSString *property_nameString = [[NSString alloc] initWithUTF8String:property_name];
        NSString *property_typeString = [[NSString alloc] initWithUTF8String:property_type];
        
        NSArray *attributes = [property_typeString componentsSeparatedByString:@","];
        YRelease(property_typeString)
        
        NSString *typeAttribute = [attributes objectAtIndex:0];
        
        [column_name_array addObject:property_nameString];
        YRelease(property_nameString)
        
        //NSLog(@"type: %c", property_type[1]);
        switch(property_type[1]) {
            case 'i' : //int
                [column_type_array addObject:@"INTEGER"];
                break;
            case 'f' : //float
                [column_type_array addObject:@"REAL"];
                break;
            case 'd' : //double
                [column_type_array addObject:@"REAL"];
                break;
            case 'q' : //NSInteger
                [column_type_array addObject:@"INTEGER"];
                break;
            case 'c' : //BOOL
                [column_type_array addObject:@"BOOL"];
                break;
            case '@' : //ObjC object
            {
                NSString *typeClassString = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)];
                if ([typeClassString isEqualToString:@"NSString"]) {
                    [column_type_array addObject:@"TEXT"];
                } else if ([typeClassString isEqualToString:@"NSDate"]) {
                    [column_type_array addObject:@"DATETIME"];
                } else if ([typeClassString isEqualToString:@"NSData"]) {
                    [column_type_array addObject:@"BLOB"];
                }
            }
                break;
        }
        [nameTypeDic setObject:[column_type_array objectAtIndex:i+1] forKey:[column_name_array objectAtIndex:i+1]];
    } 
    
    free(properties);
    NSString *sql = [NSString stringWithFormat:@"%@", sqlString];
    
    [dbProvider open];
    
    if ([dbProvider dbIsNil]) {
        DebugLog(@"[dbProvider dbIsNil]");
    }
    id res = [dbProvider executeQuery:sql withArgument:column_name_array];
    
    for(id obj in res) {
        id createdClass = [[clazz alloc] init];
        Entity *entity = (Entity *)createdClass;
        for (id key in obj) {
            NSString *type = [nameTypeDic objectForKey:key];
            id value = [obj objectForKey:key];
            if([key isEqualToString:@"rowid"]){
                [entity setRowId: value];
            } else if([type isEqualToString:@"INTEGER"]) {
                [createdClass setValue: [NSNumber numberWithInt:[value intValue]] forKey:key];
            } else if ([type isEqualToString:@"REAL"]) {
                [createdClass setValue: [NSNumber numberWithDouble:[value doubleValue]] forKey:key];
            } else if ([type isEqualToString:@"BOOL"]) {
                [createdClass setValue: [NSNumber numberWithBool:[value boolValue]] forKey:key];
            } else if ([type isEqualToString:@"Integer"]){
                [createdClass setValue: [NSNumber numberWithInteger:[value integerValue]] forKey:key];
            } else if ([type isEqualToString:@"TEXT"]){
                [createdClass setValue: (NSString *)value forKey:key]; 
            } else if ([type isEqualToString:@"DATETIME"]){
                [createdClass setValue: (NSDate *)value forKey:key]; 
            } else if ([type isEqualToString:@"BLOB"]){
                [createdClass setValue: (NSData *)value forKey:key]; 
            }
        }
        [entity setDbProvider:dbProvider];
        [entity setTable_name: className];
        [entity setColumn_name_array: column_name_array];
        [entity setColumn_type_array: column_type_array];
        [resultEntities addObject:entity];
        //    createdClass = nil;
        YRelease(createdClass)
    }
    
    //  column_name_array = nil;
    //  column_type_array = nil;
    
    return resultEntities ;
}


- (NSArray *) fieldsByTable:(NSString *) className  field:(NSString *) field withfliter:(NSString *) filterString
{
    if ([NSString isNilOrEmpty:className]) {
        return nil;
    }
    
    if ([NSString isNilOrEmpty:field]) {
        return nil;
    }
    
    if ([NSString isNilOrEmpty:filterString]) {
        return nil;
    }
    
    NSString * sql = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@",
                      field,
                      [EntityManager getTableNameWithClassName:className],
                      filterString];
    NSArray * column_name_array = [NSArray arrayWithObjects:field, nil];
    NSMutableArray * result = [[[NSMutableArray alloc] init] autorelease];
    [dbProvider open];
    id res = [dbProvider executeQuery:sql withArgument:column_name_array];
    for(id obj in res) {       
        for (id key in obj) {
            id value = [obj objectForKey:key];
            if([key isEqualToString:field])
            {
                [result addObject:value];
            }
        }
    }
    
    return result;
    
}


- (NSArray *) rowidsByTable:(NSString *)className withfilter:(NSString *)filterString
{
    if ([NSString isNilOrEmpty:className] ||
        [NSString isNilOrEmpty:filterString]) {
        return nil;
    }
    
    
    return [self fieldsByTable:className field:@"rowid" withfliter:filterString];
    
    NSString * sql = [NSString stringWithFormat:@"SELECT rowid FROM %@ WHERE %@", [EntityManager getTableNameWithClassName:className], filterString];
    
    NSArray * column_name_array = [NSArray arrayWithObjects:@"rowid", nil];
    NSMutableArray * result = [[[NSMutableArray alloc] init] autorelease];
    [dbProvider open];
    id res = [dbProvider executeQuery:sql withArgument:column_name_array];
    for(id obj in res) {       
        for (id key in obj) {
            id value = [obj objectForKey:key];
            if([key isEqualToString:@"rowid"])
            {
                [result addObject:value];
            }
        }
    }
    
    return result;
}
- (void) delete:(NSString *)className withSql:(NSString *)sqlString {
    // check if a table exists
    NSString *esql = [NSString stringWithFormat:@"SELECT COUNT(NAME) FROM SQLITE_MASTER WHERE type='table' AND NAME='%@'", 
                      [EntityManager getTableNameWithClassName: className]];
    
    // return 0 is table does not exist, 1 if it does.
    [dbProvider open];
    NSInteger table_exist_code = [[dbProvider executeQuery:esql withArgument:@"0x00"] integerValue];
    if (table_exist_code == 1) 
    {
        [dbProvider executeQuery:sqlString withArgument:nil];
    }
}


- (NSInteger) countByTable:(NSString *)className
{
    NSString * sql = [NSString stringWithFormat:@"SELECT COUNT(*) FROM %@",[EntityManager getTableNameWithClassName:className]];
    
    [dbProvider open];
    NSInteger count = [[dbProvider executeQuery:sql withArgument:@"0x00"] integerValue];
    return count;
}
@end




